package com.zeny.express.framework.shared.encoder;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.zeny.express.framework.shared.exception.ApplicationException;

/**
 * AES加解密
 * 
 * @author zhuwei
 * @date 2013年8月20日 下午9:03:44
 */
public class AESEncoder {

	/**
	 * 日志
	 */
	protected static final Log LOG = LogFactory.getLog(AESEncoder.class);

	/**
	 * 获取加密Cipher
	 * 
	 * @author zhuwei
	 * @date 2013年9月10日 下午7:00:47
	 * @param keyBytes
	 * @return
	 */
	public static Cipher getEncryptCipher(byte[] keyBytes) {
		Cipher cipher = null;
		try {
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, key);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}

		return cipher;
	}

	/**
	 * 获取解密Cipher
	 * 
	 * @author zhuwei
	 * @date 2013年9月10日 下午7:01:02
	 * @param keyBytes
	 * @return
	 */
	public static Cipher getDecryptCipher(byte[] keyBytes) {

		Cipher cipher = null;
		try {
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			cipher.init(Cipher.DECRYPT_MODE, key);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}

		return cipher;
	}

	/**
	 * 使用AES/ECB/PKCS5Padding进行加密
	 * 
	 * @param keyBytes
	 * @param dataBytes
	 * @return
	 * @throws Exception
	 */
	public static byte[] encrypt(byte[] keyBytes, byte[] dataBytes)
			throws Exception {

		Cipher cipher = getEncryptCipher(keyBytes);
		return encrypt(cipher, dataBytes);
	}

	/**
	 * 使用AES/CBC/PKCS5Padding(向量)进行加密
	 * 
	 * @param keyBytes
	 * @param ivBytes
	 * @param dataBytes
	 * @return
	 */
	public static byte[] encrypt(byte[] keyBytes, byte[] ivBytes,
			byte[] dataBytes) throws Exception {

		Cipher cipher = null;
		try {
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
			cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}
		return encrypt(cipher, dataBytes);
	}

	/**
	 * 使用AES/ECB/PKCS5Padding进行解密
	 * 
	 * @param keyBytes
	 * @param encryptedDataBytes
	 * @return
	 */
	public static byte[] decrypt(byte[] keyBytes, byte[] encryptedDataBytes)
			throws Exception {

		Cipher cipher = getDecryptCipher(keyBytes);
		return decrypt(cipher, encryptedDataBytes);
	}

	/**
	 * 使用AES/CBC/PKCS5Padding(向量)进行解密
	 * 
	 * @param keyBytes
	 * @param ivBytes
	 * @param encryptedDataBytes
	 * @return
	 */
	public static byte[] decrypt(byte[] keyBytes, byte[] ivBytes,
			byte[] encryptedDataBytes) {

		Cipher cipher = null;
		try {
			SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
			IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
			cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}

		return decrypt(cipher, encryptedDataBytes);

	}

	@SuppressWarnings("resource")
	public static byte[] encrypt(Cipher cipher, byte[] dataBytes) {

		try {
			ByteArrayInputStream bIn = new ByteArrayInputStream(dataBytes);
			CipherInputStream cIn = new CipherInputStream(bIn, cipher);
			ByteArrayOutputStream bOut = new ByteArrayOutputStream();
			int ch;
			while ((ch = cIn.read()) >= 0) {
				bOut.write(ch);
			}
			return bOut.toByteArray();
		} catch (IOException e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}
	}

	public static byte[] decrypt(Cipher cipher, byte[] encryptedDataBytes) {
		try {
			ByteArrayOutputStream bOut = new ByteArrayOutputStream();
			CipherOutputStream cOut = new CipherOutputStream(bOut, cipher);
			cOut.write(encryptedDataBytes);
			cOut.close();
			return bOut.toByteArray();
		} catch (IOException e) {
			LOG.error(e.getMessage(), e);
			throw new ApplicationException(ApplicationException.UNKNOWN, null);
		}
	}

}
